/*
 * Copyright (C) 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.code.flickrlite;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

/**
 * Utility class to interact with the Flickr REST-based web services.
 * 
 * This class uses a default Flickr API key that you should replace with your
 * own if you reuse this code to redistribute it with your application(s).
 * 
 */
public class Flickr {

	static final String LOG_TAG = "Photostream";

	private final String API_KEY;

	private static final String API_REST_METHOD_URL_FORMAT = "%s://%s%s";

	private static final String API_PROTO_HTTP = "http";
	private static final String API_REST_HOST = "api.flickr.com";
	private static final String API_REST_URL = "/services/rest/";

	private static final String API_INTERESTINGNESS = "flickr.interestingness.getList";

	private static final String API_PHOTOS_GET_INFO = "flickr.photos.getInfo";
	private static final String API_PHOTOS_GET_SIZES = "flickr.photos.getSizes";

	private static final String API_PEOPLE_FIND_BY_USERNAME = "flickr.people.findByUsername";
	private static final String API_PEOPLE_GET_INFO = "flickr.people.getInfo";
	private static final String API_PEOPLE_GET_PUBLIC_PHOTOS = "flickr.people.getPublicPhotos";
	private static final String API_PEOPLE_GET_LOCATION = "flickr.photos.geo.getLocation";

	private static final String PARAM_API_KEY = "api_key";
	private static final String PARAM_METHOD = "method";
	private static final String PARAM_DATE = "date";
	private static final String PARAM_USERNAME = "username";
	private static final String PARAM_USERID = "user_id";
	private static final String PARAM_PER_PAGE = "per_page";
	private static final String PARAM_PAGE = "page";
	private static final String PARAM_EXTRAS = "extras";
	private static final String PARAM_PHOTO_ID = "photo_id";
	private static final String PARAM_FEED_ID = "id";
	private static final String PARAM_FEED_FORMAT = "format";

	private static final String VALUE_DEFAULT_EXTRAS = "date_taken";
	private static final String VALUE_DEFAULT_FORMAT = "atom";

	private static final String RESPONSE_TAG_RSP = "rsp";
	private static final String RESPONSE_ATTR_STAT = "stat";
	private static final String RESPONSE_STATUS_OK = "ok";
	private static final String RESPONSE_STATUS_FAIL = "fail";

	private static final String RESPONSE_TAG_ERROR = "err";
	private static final String RESPONSE_ATTR_CODE = "code";
	private static final String RESPONSE_ATTR_MESSAGE = "msg";

	private static final String RESPONSE_TAG_USER = "user";
	private static final String RESPONSE_ATTR_NSID = "nsid";

	private static final String RESPONSE_TAG_PHOTOS = "photos";
	private static final String RESPONSE_ATTR_PAGE = "page";
	private static final String RESPONSE_ATTR_PAGES = "pages";

	private static final String RESPONSE_TAG_PHOTO = "photo";
	private static final String RESPONSE_ATTR_ID = "id";
	private static final String RESPONSE_ATTR_SECRET = "secret";
	private static final String RESPONSE_ATTR_SERVER = "server";
	private static final String RESPONSE_ATTR_FARM = "farm";
	private static final String RESPONSE_ATTR_TITLE = "title";
	private static final String RESPONSE_ATTR_DATE_TAKEN = "datetaken";
	private static final String RESPONSE_ATTR_DATE_UPLOADED = "dateuploaded";
	private static final String RESPONSE_ATTR_ORIGINAL_SECRET = "originalsecret";
	private static final String RESPONSE_ATTR_ORIGINAL_FORMAT = "originalformat";

	private static final String RESPONSE_TAG_PERSON = "person";
	private static final String RESPONSE_ATTR_ISPRO = "ispro";
	private static final String RESPONSE_ATTR_ICONSERVER = "iconserver";
	private static final String RESPONSE_ATTR_ICONFARM = "iconfarm";
	private static final String RESPONSE_TAG_USERNAME = "username";
	private static final String RESPONSE_TAG_REALNAME = "realname";
	private static final String RESPONSE_TAG_LOCATION = "location";
	private static final String RESPONSE_ATTR_LATITUDE = "latitude";
	private static final String RESPONSE_ATTR_LONGITUDE = "longitude";
	private static final String RESPONSE_TAG_PHOTOSURL = "photosurl";
	private static final String RESPONSE_TAG_PROFILEURL = "profileurl";
	private static final String RESPONSE_TAG_MOBILEURL = "mobileurl";

	private static final String RESPONSE_TAG_FEED = "feed";
	private static final String RESPONSE_TAG_UPDATED = "updated";

	private static final String RESPONSE_TAG_SIZES = "sizes";

	private static final String RESPONSE_TAG_SIZE = "size";
	private static final String RESPONSE_ATTR_LABEL = "label";
	private static final String RESPONSE_ATTR_WIDTH = "width";
	private static final String RESPONSE_ATTR_HEIGHT = "height";
	private static final String RESPONSE_ATTR_SOURCE = "source";
	private static final String RESPONSE_ATTR_URL = "url";

	private final DocumentBuilderFactory dbFactory;

	private static final Logger logger = Logger.getLogger(Flickr.class
			.getName());




	public Flickr(String apiKey) {

		API_KEY = apiKey;

		dbFactory = DocumentBuilderFactory.newInstance();
		dbFactory.setNamespaceAware(true);
	}

	public List<Photo> getInterestingness(int perPage, int pageNum,
			boolean detailedScan) throws Exception {

		UrlBuilder urlBuilder = new UrlBuilder(API_REST_HOST, API_REST_URL);

		urlBuilder.addQueryParam(PARAM_METHOD, API_INTERESTINGNESS);
		urlBuilder.addQueryParam(PARAM_PER_PAGE, String.valueOf(perPage));
		urlBuilder.addQueryParam(PARAM_PAGE, String.valueOf(pageNum));

		final List<Photo> photos = new ArrayList<Photo>();

		executeRequest(urlBuilder, new ResponseHandler() {
			public void handleResponse(InputStream in) {
				try {
					parseResponse(in, new ResponseParser() {
						public void parseResponse(Node node)
								throws SAXException, IOException {
							parsePhotos(node, photos);
						}
					});
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
//
//		if (detailedScan) {
//			for (int i = 0; i < photos.size(); i++) {
//
//				final Photo photo = photos.get(i);
//
//				urlBuilder = new UrlBuilder(API_REST_HOST, API_REST_URL);
//
//				urlBuilder.addQueryParam(PARAM_METHOD, API_PHOTOS_GET_INFO);
//				urlBuilder.addQueryParam(PARAM_PHOTO_ID, photo.getId());
//
//				executeRequest(urlBuilder, new ResponseHandler() {
//					public void handleResponse(InputStream in)
//							throws IOException {
//						parseResponse(in, new ResponseParser() {
//							public void parseResponse(Node node)
//									throws SAXException, IOException {
//
//								for (int i = 0; i < node.getChildNodes()
//										.getLength(); i++) {
//									Node tmpNode = node.getChildNodes().item(i);
//
//									if (tmpNode instanceof Element
//											&& RESPONSE_TAG_PHOTO
//													.equals(tmpNode
//															.getNodeName())) {
//										parsePhotoInfo(tmpNode, photo);
//										break;
//									}
//								}
//							}
//						});
//					}
//				});
//			}
//		}

		return photos;
	}

	public Photo getPhotoInfo(String id) throws Exception {

		Photo photo = new Photo();
		photo.setId(id);

		getPhotoInfo(photo);

		return photo;
	}

	public void getPhotoInfo(final Photo photo) throws Exception {

		UrlBuilder urlBuilder = new UrlBuilder(API_REST_HOST, API_REST_URL);

		urlBuilder.addQueryParam(PARAM_METHOD, API_PHOTOS_GET_INFO);
		urlBuilder.addQueryParam(PARAM_PHOTO_ID, photo.getId());

		executeRequest(urlBuilder, new ResponseHandler() {
			public void handleResponse(InputStream in) throws IOException {
				parseResponse(in, new ResponseParser() {
					public void parseResponse(Node node) throws SAXException,
							IOException {

						for (int i = 0; i < node.getChildNodes().getLength(); i++) {
							Node tmpNode = node.getChildNodes().item(i);

							if (tmpNode instanceof Element
									&& RESPONSE_TAG_PHOTO.equals(tmpNode
											.getNodeName())) {
								parsePhotoInfo(tmpNode, photo);
								break;
							}
						}
					}
				});
			}
		});
	}

	public void getPhotoSizes(final Photo photo) throws Exception {

		UrlBuilder urlBuilder = new UrlBuilder(API_REST_HOST, API_REST_URL);

		urlBuilder.addQueryParam(PARAM_METHOD, API_PHOTOS_GET_SIZES);
		urlBuilder.addQueryParam(PARAM_PHOTO_ID, photo.getId());

		executeRequest(urlBuilder, new ResponseHandler() {
			public void handleResponse(InputStream in) throws IOException {
				parseResponse(in, new ResponseParser() {
					public void parseResponse(Node node) throws SAXException,
							IOException {

						for (int i = 0; i < node.getChildNodes().getLength(); i++) {
							Node tmpNode = node.getChildNodes().item(i);

							if (tmpNode instanceof Element
									&& RESPONSE_TAG_SIZES.equals(tmpNode
											.getNodeName())) {
								parseSizes(tmpNode, photo);
								break;
							}
						}
					}
				});
			}
		});
	}

	/**
	 * Parses a valid Flickr XML response from the specified input stream. When
	 * the Flickr response contains the OK tag, the response is sent to the
	 * specified response parser.
	 * 
	 * @param in
	 *            The input stream containing the response sent by Flickr.
	 * @param responseParser
	 *            The parser to use when the response is valid.
	 */
	private void parseResponse(InputStream in, ResponseParser responseParser)
			throws IOException {

		DocumentBuilder builder;
		try {
			builder = dbFactory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			throw new IOException("Couldn't initialise builder: "
					+ e.getMessage());
		}

		StringBuilder sb = new StringBuilder();
		String line;

		BufferedReader reader = new BufferedReader(new InputStreamReader(in,
				"UTF-8"));

		while ((line = reader.readLine()) != null) {
			sb.append(line).append("\n");
		}

		String tmpStr = sb.toString();

		logger.info(tmpStr);

		Document doc;
		ByteArrayInputStream bs = new ByteArrayInputStream(tmpStr.getBytes());
		try {

			doc = builder.parse(bs);
			Node rootNode = doc.getFirstChild();

			if (!RESPONSE_TAG_RSP.equals(rootNode.getNodeName())) {
				throw new IOException("Wrong root element name: "
						+ rootNode.getNodeName());
			}

			final String value = rootNode.getAttributes().getNamedItem(
					RESPONSE_ATTR_STAT).getTextContent();
			if (!RESPONSE_STATUS_OK.equals(value)) {
				throw new IOException("Wrong status: " + value);
			}

			responseParser.parseResponse(rootNode);

		} catch (SAXException e) {
			throw new IOException("XML parsing exception occurred: "
					+ e.getMessage());
		} finally {
			bs.close();
		}
	}

	/**
	 * Executes an HTTP request on Flickr's web service. If the response is ok,
	 * the content is sent to the specified response handler.
	 * 
	 * @param handler
	 *            The handler which will parse the response.
	 * 
	 * @throws IOException
	 */
	private void executeRequest(UrlBuilder urlBuilder, ResponseHandler handler)
			throws Exception {

		urlBuilder.addQueryParam(PARAM_API_KEY, API_KEY);

		URL url = new URL(urlBuilder.getURL());

		logger.info("executing request: " + url);

		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setRequestMethod("GET");
		conn.connect();

		InputStream in = null;
		try {
			in = conn.getInputStream();
			handler.handleResponse(in);

		} finally {
			if (in != null) {
				in.close();
			}
		}
	}

	private void parsePhotos(Node node, List<Photo> photos) throws SAXException,
			IOException {

		Node photosNode = null;

		for (int i = 0; i < node.getChildNodes().getLength(); i++) {

			Node tmpNode = node.getChildNodes().item(i);

			if (tmpNode instanceof Element
					&& RESPONSE_TAG_PHOTOS.equals(tmpNode.getNodeName())) {
				photosNode = node.getChildNodes().item(i);
			}
		}

		if (photosNode != null) {

//			photos.setPage(Integer.parseInt(photosNode.getAttributes()
//					.getNamedItem(RESPONSE_ATTR_PAGE).getTextContent()));
//			photos.setPageCount(Integer.parseInt(photosNode.getAttributes()
//					.getNamedItem(RESPONSE_ATTR_PAGES).getTextContent()));

			for (int i = 0; i < photosNode.getChildNodes().getLength(); i++) {
				Node photoNode = photosNode.getChildNodes().item(i);

				if (photoNode instanceof Element) {
					if (RESPONSE_TAG_PHOTO.equals(photoNode.getNodeName())) {
						Photo photo = new Photo();
						photos.add(photo);
						parsePhotoInfo(photoNode, photo);
					} else {
						logger.log(Level.WARNING, "Unexpected node: "
								+ photoNode.getNodeName() + " (asserted: "
								+ RESPONSE_TAG_PHOTO + ")");
					}
				}
			}
		} else {
			throw new IOException(RESPONSE_TAG_PHOTOS + " node not found");
		}

	}

//	private void parsePhoto(Node node, Photo photo) throws SAXException,
//			IOException {
//
//		String name = node.getNodeName();
//
//		if (node instanceof Element && RESPONSE_TAG_PHOTO.equals(name)) {
//
//			Node tmpNode = null;
//
//			photo.setId(node.getAttributes().getNamedItem(RESPONSE_ATTR_ID)
//					.getTextContent());
//			photo.setSecret(node.getAttributes().getNamedItem(
//					RESPONSE_ATTR_SECRET).getTextContent());
//			photo.setServer(node.getAttributes().getNamedItem(
//					RESPONSE_ATTR_SERVER).getTextContent());
//			photo.setFarm(node.getAttributes().getNamedItem(RESPONSE_ATTR_FARM)
//					.getTextContent());
//			photo.setTitle(node.getAttributes().getNamedItem(
//					RESPONSE_ATTR_TITLE).getTextContent());
//
//			tmpNode = node.getAttributes().getNamedItem(RESPONSE_ATTR_DATE_TAKEN);
//			if (tmpNode != null) {
//				photo.setDate(tmpNode.getTextContent());
//			}
//
//		} else {
//			throw new IOException("Wrong node name: " + name + " (required: "
//					+ RESPONSE_TAG_PHOTO + ")");
//		}
//	}

	private void parsePhotoInfo(Node photoNode, Photo photo)
			throws SAXException, IOException {

		String name = photoNode.getNodeName();

		if (photoNode instanceof Element && RESPONSE_TAG_PHOTO.equals(name)) {

			Node tmpNode = null;

			photo.setId(photoNode.getAttributes()
					.getNamedItem(RESPONSE_ATTR_ID).getTextContent());
			photo.setSecret(photoNode.getAttributes().getNamedItem(
					RESPONSE_ATTR_SECRET).getTextContent());
			photo.setServer(photoNode.getAttributes().getNamedItem(
					RESPONSE_ATTR_SERVER).getTextContent());
			photo.setFarm(photoNode.getAttributes().getNamedItem(
					RESPONSE_ATTR_FARM).getTextContent());
			
			tmpNode = photoNode.getAttributes().getNamedItem(
					RESPONSE_ATTR_ORIGINAL_SECRET);
			if (tmpNode != null) {
				photo.setOriginalSecret(tmpNode.getTextContent());
			}
			
			tmpNode = photoNode.getAttributes().getNamedItem(
					RESPONSE_ATTR_ORIGINAL_FORMAT);
			if (tmpNode != null) {
				photo.setOriginalFormat(tmpNode.getTextContent());
			}
			
			tmpNode = photoNode.getAttributes().getNamedItem(
					RESPONSE_ATTR_TITLE);
			if (tmpNode != null) {
				photo.setTitle(tmpNode.getTextContent());
			}
			
			/* TODO: add dateuploaded */
			tmpNode = photoNode.getAttributes().getNamedItem(RESPONSE_ATTR_DATE_TAKEN);
			if (tmpNode != null) {
				photo.setDate(tmpNode.getTextContent());
			}

		} else {
			throw new IOException("Wrong node name: " + name + " (required: "
					+ RESPONSE_TAG_PHOTO + ")");
		}

	}

	private void parseSizes(Node sizesNode, Photo photo) throws SAXException,
			IOException {

		if (sizesNode != null) {

			for (int i = 0; i < sizesNode.getChildNodes().getLength(); i++) {
				Node sizeNode = sizesNode.getChildNodes().item(i);

				if (sizeNode instanceof Element) {
					if (RESPONSE_TAG_SIZE.equals(sizeNode.getNodeName())) {

						PhotoSize photoSize = PhotoSize.valueOf(sizeNode
								.getAttributes().getNamedItem(
										RESPONSE_ATTR_LABEL).getTextContent());
						int width = Integer.parseInt(sizeNode.getAttributes()
								.getNamedItem(RESPONSE_ATTR_WIDTH)
								.getTextContent());
						int height = Integer.parseInt(sizeNode.getAttributes()
								.getNamedItem(RESPONSE_ATTR_HEIGHT)
								.getTextContent());

						if (photoSize != null) {
							photo.getSizes().put(photoSize,
									new Dimesions(width, height));
						}

					} else {
						logger.log(Level.WARNING, "Unexpected node: "
								+ sizeNode.getNodeName() + " (asserted: "
								+ RESPONSE_TAG_PHOTO + ")");
					}
				}
			}
		} else {
			throw new IOException(RESPONSE_TAG_SIZES + " node not found");
		}

	}

	private static interface ResponseHandler {
		/**
		 * Processes the responses sent by the HTTP server following a GET
		 * request.
		 */
		public void handleResponse(InputStream in) throws IOException;
	}

	private static interface ResponseParser {
		/**
		 * Processes the XML response sent by the Flickr web service after a
		 * successful request.
		 */
		public void parseResponse(Node node) throws SAXException, IOException;
	}
}
